package com.xinzhitong.www.service.impl;

import com.xinzhitong.www.exception.QuestionBankException;
import com.xinzhitong.www.exception.TestException;
import com.xinzhitong.www.exception.questionerr.QuestionAnswerIsNullException;
import com.xinzhitong.www.exception.test.EmptyQuestionOfTestException;
import com.xinzhitong.www.exception.test.EmptyTestOfTheCredentialException;
import com.xinzhitong.www.exception.test.NullTestQuestionException;
import com.xinzhitong.www.exception.test.OutOfTimeException;
import com.xinzhitong.www.mapper.QuestionBankMapper;
import com.xinzhitong.www.mapper.TestMapper;
import com.xinzhitong.www.pojo.*;
import com.xinzhitong.www.service.TestService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

/**
 * 测试服务实现类
 *
 * @author 徐鑫 魏洛淳 钟伟良
 */
@Service
@Transactional
public class TestServiceImpl implements TestService {

    @Resource
    TestMapper testMapper;

    @Resource
    QuestionBankMapper questionBankMapper;

    /**
     * 问题类型
     */
    private static final String[] questionTypes = new String[]{
            "单选",
            "多选",
            "判断",
            "填空",
            "论述"
    };

    /**
     * 题目缓存<用户名, 题目id列表>，题目id的下标即题目的题号
     */
    private static Cache<String, BlocksOfTest> questionCache;

    private static Cache<String, Integer> testIdCache;

    static {
        //初始化缓存
        //缓存管理器
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:Test-ehcache-setting.xml");
        questionCache = cacheManager.getCache("testCache");
        testIdCache = cacheManager.getCache("testIdCache");
    }

    /**
     * 获取某用户的当前测试的题目块
     *
     * @param username 用户名
     * @return 题目块
     */
    private synchronized static BlocksOfTest getQuestionIdsOfTestOfSomeone(String username) {
        BlocksOfTest blocksOfTest = questionCache.get(username);
        //若题目块为null
        if (blocksOfTest == null)
            throw new OutOfTimeException();
        return blocksOfTest;
    }

    /**
     * 保存某用户当前测试的题目块
     *
     * @param username     用户名
     * @param blocksOfTest 题目块
     */
    private synchronized static void saveQuestionIdsOfTestOfSomeone(String username, BlocksOfTest blocksOfTest) {
        questionCache.put(username, blocksOfTest);
    }

    /**
     * 获取测试中某题的id
     *
     * @param username     用户名
     * @param questionType 题目类型
     * @param index        索引
     * @return 题号
     */
    @Override
    public synchronized Integer getQuestionIdOfTest(String username, String questionType, Integer index) {
        BlocksOfTest blocksOfTest = getQuestionIdsOfTestOfSomeone(username);
        //若题目块为null
        if (blocksOfTest == null)
            throw new OutOfTimeException();
        return blocksOfTest.getBlock().get(questionType)[index];
    }

    @Override
    public synchronized TestInfo getTestInfo(Integer credentialId) throws TestException {
        TestInfo testInfo = testMapper.selectRandomTestInfoByCredential(credentialId);
        if (testInfo == null)
            throw new EmptyTestOfTheCredentialException();
        return testInfo;
    }

    @Override
    public List<Map<String, Object>> getQuestionBlocksOfTest(Integer testId) {

        BlocksOfTest blocksOfTest = new BlocksOfTest();
        blocksOfTest.setBlock(new HashMap<>());

        List<Map<String, Object>> res = new ArrayList<>();

        int count = 0;
        //按照类型将题号取出
        for (String questionType : questionTypes) {
            List<Integer> questionIds = testMapper.selectQuestionIdOfTestByType(testId, questionType);
            //计数
            count += questionIds.size();
            //添加一个块
            blocksOfTest.addBlock(questionType, questionIds);
            //块信息添加
            Map<String, Object> tmpMap = new HashMap<>();
            tmpMap.put("questionType", questionType);
            tmpMap.put("questionSize", questionIds.size());
            res.add(tmpMap);
        }

        if (count <= 0)
            throw new EmptyQuestionOfTestException();

        //缓存记录
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        testIdCache.put(user.getUsername(), testId);
        saveQuestionIdsOfTestOfSomeone(user.getUsername(), blocksOfTest);

        return res;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Map<String, Object> readTest(
            Map<String, List<Map<String, Object>>> questions
    ) throws QuestionBankException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        Map<String, Object> map = new HashMap<>();
        List<Boolean> judgeAnswer = new ArrayList<>();
        List<String> questionTypeList = new ArrayList<>();
        double score = 0;
        try {
            for (int i = 0; i < questions.get("questions").size(); i++) {
                String questionType = questions.get("questions").get(i).get("questionType").toString();
                Integer index = Integer.valueOf(questions.get("questions").get(i).get("index").toString());
                List<String> userQuestionAnswer =
                        (List<String>) questions.get("questions").get(i).get("userQuestionAnswer");
                Integer questionId = getQuestionIdOfTest(user.getUsername(), questionType, index);
                Integer testId = testIdCache.get(user.getUsername());
                List<Map<String, Object>> questionAnswer =
                        questionBankMapper.selectAnswerIsCorrect(questionId, questionType);
                TestHasQuestion testHasQuestion = testMapper.selectQuestionScore(testId, questionId);
                questionTypeList.add(questionType);
                if (userQuestionAnswer.size() == 0) {
                    judgeAnswer.add(FALSE);
                } else {
                    //判断题
                    if (questionType.equals("判断")) {
                        if (questionAnswer.get(0).get("judgeIsCorrect") == null) {
                            throw new QuestionAnswerIsNullException();
                        } else {
                            if (Boolean.valueOf(userQuestionAnswer.get(0)) ==
                                    questionAnswer.get(0).get("judgeIsCorrect")) {
                                judgeAnswer.add(TRUE);
                                score += testHasQuestion.getScore();
                            } else {
                                judgeAnswer.add(FALSE);
                            }
                        }

                        //单选题
                    } else if (questionType.equals("单选")) {
                        List<Map<String, Object>> optionCode = questionBankMapper.selectOptionCode(questionId, TRUE);
                        if (optionCode.size() == 0) {
                            throw new QuestionAnswerIsNullException();
                        } else {
                            if (userQuestionAnswer.get(0).equals(optionCode.get(0).get("optionCode"))) {
                                judgeAnswer.add(TRUE);
                                score += testHasQuestion.getScore();
                            } else {
                                judgeAnswer.add(FALSE);
                            }
                        }

                        //多选题
                    } else {
                        List<Map<String, Object>> optionCode = questionBankMapper.selectOptionCode(questionId, TRUE);
                        if (optionCode.size() == 0) {
                            throw new QuestionAnswerIsNullException();
                        } else {
                            List<String> correctAnswer = new ArrayList<>();
                            for (Map<String, Object> stringObjectMap : optionCode) {
                                correctAnswer.add(stringObjectMap.get("optionCode").toString());
                            }
                            if (userQuestionAnswer.equals(correctAnswer)) {
                                judgeAnswer.add(TRUE);
                                score += testHasQuestion.getScore();
                            } else {
                                judgeAnswer.add(FALSE);
                            }
                        }
                    }
                }
            }
            map.put("judgeAnswer", judgeAnswer);
            map.put("score", score);
            map.put("questionType", questionTypeList);
            return map;
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new QuestionAnswerIsNullException();
        }
    }

    @Override
    public Map<String, Object> getTestQuestion(Integer index, String questionType) throws TestException {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        try {
            Integer questionId = getQuestionIdOfTest(user.getUsername(), questionType, index);
            Integer testId = testIdCache.get(user.getUsername());
            String questionText = testMapper.selectQuestionText(questionId);
            TestHasQuestion testHasQuestion = testMapper.selectQuestionScore(testId, questionId);

            Map<String, Object> map = new HashMap<>();
            map.put("questionText", questionText);
            map.put("questionScore", testHasQuestion.getScore());
            if (questionType.equals("判断")) {
                return map;
            } else {
                List<Option> options = testMapper.selectOptionText(questionId);
                List<String> optionCode = new ArrayList<>();
                List<String> optionText = new ArrayList<>();
                for (Option option : options) {
                    optionCode.add(option.getCode());
                    optionText.add(option.getText());
                }
                map.put("optionCode", optionCode);
                map.put("optionText", optionText);
                return map;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new NullTestQuestionException();
        }
    }
}
